package eolympics.client;

import java.awt.EventQueue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import jexxus.client.ConnectionToServer;
import jexxus.common.Connection;
import jexxus.common.ConnectionListener;
import jexxus.server.ConnectionToClient;
import minigames.IMiniGame;
import data.GameData;
import data.GameState;
import data.IGameDataReadOnly;
import data.Player;
import eolympics.Communicator;
import eolympics.IClientEventsListener;
import eolympics.server.GameServer;

public class GameClient
{
    private List<IClientEventsListener> mClientEventsListeners = new ArrayList<>();

    private GameData                    mGameData              = new GameData();
    private ConnectionToServer          mConnectionToServer    = new ConnectionToServer(new ConnectionToServerListener());

    public GameClient(final List<IMiniGame> pMiniGames, final IClientEventsListener pClientEventsListener)
    {
        mClientEventsListeners.add(pClientEventsListener);

        mGameData.addMiniGames(pMiniGames);
    }

    public IGameDataReadOnly getGameData()
    {
        return mGameData;
    }

    public void joinGameAt( final String pServerAddress, final String pPlayerName ) throws IOException
    {
        if( mConnectionToServer.isConnected() )
        {
            return;
        }
        mConnectionToServer.connectTo(pServerAddress, GameServer.PORT);
        Communicator.sendPlayerNameToServer(pPlayerName, mConnectionToServer);
    }

    public void disconnectFromServer()
    {
        if( mConnectionToServer.isConnected() )
        {
            mConnectionToServer.close();
        }

        if( mGameData.getCurrentMiniGame() != null )
        {
            mGameData.getCurrentMiniGame().stopForClient();
        }

        mGameData.reset();

        informAllClientListenersOfGameStateChangeOnAwtThread();
    }

    public void addClientEventsListener( final IClientEventsListener pClientEventsListener )
    {
        synchronized( mClientEventsListeners )
        {
            mClientEventsListeners.add(pClientEventsListener);
        }
    }

    public void removeClientEventsListener( final IClientEventsListener pClientEventsListener )
    {
        synchronized( mClientEventsListeners )
        {
            mClientEventsListeners.remove(pClientEventsListener);
        }
    }

    public void sendPlayerIsReadyToServer()
    {
        Communicator.sendPlayerIsReadyToServer(mConnectionToServer);
    }

    public boolean isConnected()
    {
        return mConnectionToServer.isConnected();
    }

    private void handleNewGameState( final GameState pNewGameState )
    {
        if( pNewGameState == mGameData.getGameState() )
        {
            return;
        }

        mGameData.setGameState(pNewGameState);

        startMiniGameOnRightState();

        informAllClientListenersOfGameStateChangeOnAwtThread();
    }

    private void startMiniGameOnRightState()
    {
        if( mGameData.getGameState() == GameState.PLAYING_MINI_GAME )
        {
            IMiniGame miniGame = mGameData.getCurrentMiniGame();
            miniGame.startGameForClient();
        }
    }

    private class ConnectionToServerListener
            implements ConnectionListener
    {
        @Override
        public void connectionBroken( final Connection pBroken, final boolean pForced )
        {
            System.out.println("connection to server lost: " + pForced);
            disconnectFromServer();
            informAllClientListenersOfConnectionLostOnAwtThread();
        }

        @Override
        public void receive( final byte[] pData, final Connection pServer )
        {
            String message = new String(pData);

            if( Communicator.getMessageType(message).equals(Communicator.MessageType.GAME_STATE) )
            {
                final GameState newGameState = Communicator.getGameState(message);

                handleNewGameState(newGameState);
            }
            else if( Communicator.getMessageType(message).equals(Communicator.MessageType.PLAYER_LIST) )
            {
                List<Player> playerList = Communicator.getPlayerList(message);

                mGameData.setPlayers(playerList);

                informAllClientListenersOfPlayerListChangeOnAwtThread();
            }
            else if( Communicator.getMessageType(message).equals(Communicator.MessageType.MINI_GAME_CHOSEN) )
            {
                IMiniGame chosenMinigame = Communicator.getChosenMiniGame(message, mGameData.getMiniGames());
                mGameData.setCurrentMiniGame(chosenMinigame);
            }
        }

        @Override
        public void clientConnected( final ConnectionToClient pNewClient )
        {
            throw new UnsupportedOperationException("since this is the client-method, it should never be called");
        }
    }

    private void informAllClientListenersOfConnectionLostOnAwtThread()
    {
        synchronized( mClientEventsListeners )
        {
            for( final IClientEventsListener currentClientEventsListener : mClientEventsListeners )
            {
                EventQueue.invokeLater(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        currentClientEventsListener.clientConnectionToServerLost();
                    }
                });
            }
        }
    }

    private void informAllClientListenersOfPlayerListChangeOnAwtThread()
    {
        synchronized( mClientEventsListeners )
        {
            for( final IClientEventsListener currentClientEventsListener : mClientEventsListeners )
            {
                EventQueue.invokeLater(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        currentClientEventsListener.playerListChanged(mGameData);
                    }
                });
            }
        }
    }

    private void informAllClientListenersOfGameStateChangeOnAwtThread()
    {
        synchronized( mClientEventsListeners )
        {
            for( final IClientEventsListener currentClientEventsListener : mClientEventsListeners )
            {
                EventQueue.invokeLater(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        currentClientEventsListener.gameStateChanged(mGameData);
                    }
                });
            }
        }
    }
}
